#include "database.h"
#include "assert.h"
#include "globals.h"

/////////////////////////////////// Db string begin /////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////

DbString::DbString( DbConnection & dbConnection, sqlite3_stmt *& stmt, const QString & text )
:   m_dbConnection( dbConnection ),
    m_hasError( false ),
    m_boundIndex( 1 ),
    m_stmt( stmt )
{
    if (m_stmt != NULL && sqlite3_reset( m_stmt ) != SQLITE_OK)
    {
        m_errorString = sqlite3_errmsg( m_dbConnection.connectionPtr() );
        m_hasError = true;
    }

    if (sqlite3_prepare( m_dbConnection.connectionPtr(), text.toUtf8().data(), -1, & m_stmt, NULL) != SQLITE_OK)
    {
        m_errorString = sqlite3_errmsg( m_dbConnection.connectionPtr() );
        m_hasError = true;
    }
}

DbString::~DbString()
{
}

DbString & DbString::operator=( const DbString & dbString )
{
    m_dbConnection = dbString.m_dbConnection;
    m_stmt = dbString.m_stmt;
    m_hasError = dbString.m_hasError;
    m_errorString = dbString.m_errorString;
    m_boundIndex = 1;
    return * this;
}

bool DbString::hasError() const
{
    return m_hasError;
}

const QString & DbString::errorString() const
{
    return m_errorString;
}

DbString & DbString::argValue( const quint32 & value )
{
    if (sqlite3_bind_int( m_stmt, m_boundIndex, value ) != SQLITE_OK)
        m_errorString = QString( "Binding quint32 %1 failed." ).arg( value );

    m_boundIndex ++;
    return * this;
}

DbString & DbString::argValue( const int & value )
{
    if (sqlite3_bind_int( m_stmt, m_boundIndex, value ) != SQLITE_OK)
        m_errorString = QString( "Binding int %1 failed." ).arg( value );

    m_boundIndex ++;
    return * this;
}

DbString & DbString::argValue( const qint64 & value )
{
    if (sqlite3_bind_int64( m_stmt, m_boundIndex, value ) != SQLITE_OK)
        m_errorString = QString( "Binding qint64 %1 failed." ).arg( value );

    m_boundIndex ++;
    return * this;
}

DbString & DbString::argValue( const double & value )
{
    if (sqlite3_bind_double( m_stmt, m_boundIndex, value ) != SQLITE_OK)
        m_errorString = QString( "Binding double %1 failed." ).arg( value );

    m_boundIndex ++;
    return * this;
}

DbString & DbString::argValue( const QDate & value )
{
    if (sqlite3_bind_int64( m_stmt, m_boundIndex, value.toJulianDay() ) != SQLITE_OK)
        m_errorString = QString( "Binding QDate %1 failed." ).arg( value.toString() );

    m_boundIndex ++;
    return * this;
}

DbString & DbString::argValue( const QDateTime & value )
{
    if (sqlite3_bind_int64( m_stmt, m_boundIndex, value.toMSecsSinceEpoch() ) != SQLITE_OK)
        m_errorString = QString( "Binding QDateTime %1 failed." ).arg( value.toString() );

    m_boundIndex ++;
    return * this;
}

DbString & DbString::argValue( const QString & value )
{
    QByteArray utf8String = value.toUtf8();
    if (sqlite3_bind_text( m_stmt, m_boundIndex, utf8String.data(), utf8String.count(), SQLITE_TRANSIENT ) != SQLITE_OK)
        m_errorString = QString( "Binding QString %1 failed." ).arg( value );

    m_boundIndex ++;
    return * this;
}

DbString & DbString::argValue( const QByteArray & value )
{
    if (sqlite3_bind_blob( m_stmt, m_boundIndex, value.data(), value.count(), SQLITE_TRANSIENT ) != SQLITE_OK)
        m_errorString = QString( "Binding blob failed." );

    m_boundIndex ++;
    return * this;
}

//////////////////////////////////// Db string end //////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////

DbConnection & DbConnection::dbConnection()
{
    static DbConnection dbConn;
    return dbConn;
}

DbConnection::DbConnection()
:   m_connPtr( NULL )
{
    connect();
}

DbConnection::~DbConnection()
{
    sqlite3_close( m_connPtr );
    m_connPtr = NULL;
}

bool DbConnection::connect()
{
    if (m_connPtr == NULL)
    {
        return _connect();
    }
    else
    {
        return true;
    }
}
   
bool DbConnection::_connect()
{
//#ifdef _LCC
//    const LccConfig & conf = LccConfig::lccConfig();
//#else _HASP_UPDATER
//    const HaspUpdaterConfig & conf = HaspUpdaterConfig::haspUpdaterConfig();
//#endif
//
//    const DbParams & dbParams = conf.m_dbParams;
//    QByteArray dbPort = QString( "%1" ).arg( dbParams.m_dbport ).toUtf8();
//    m_connPtr = PQsetdbLogin( dbParams.m_dbhost, dbPort.data(), NULL, NULL, dbParams.m_dbname, dbParams.m_dbuser, dbParams.m_dbpassword );
//    ConnStatusType connStatusType = PQstatus( m_connPtr );
//    if (connStatusType == CONNECTION_OK)
//        return true;
    QString fileName( "gamedata.db" );

    if (sqlite3_open( fileName.toUtf8().data(), & m_connPtr ) == SQLITE_OK)
        return true;

    m_lastError = sqlite3_errmsg( m_connPtr );
    sqlite3_close( m_connPtr );
    m_connPtr = NULL;
    return false;
}

const QString & DbConnection::getLastError() const
{
    return m_lastError;
}

sqlite3 * DbConnection::connectionPtr()
{
    return m_connPtr;
}

//////////////////////////////////////// SqlCommand //////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////

SqlCommand::SqlCommand()
:   m_commited( false ),
    m_isInitialized( false ),
    m_dbConnection( DbConnection::dbConnection() )
{
    m_isInitialized = init();
}

SqlCommand::SqlCommand( DbConnection & dbConnection )
:   m_commited( false ),
    m_isInitialized( false ),
    m_dbConnection( dbConnection )
{
    m_isInitialized = init();
}

SqlCommand::~SqlCommand()
{
    if (! m_commited)
    { 
        QString errorStr;
        _exec( "ROLLBACK", errorStr );
    }

    if (m_stmt != NULL)
    {
        sqlite3_finalize( m_stmt );
        m_stmt = NULL;
    }
}

sqlite3_stmt *& SqlCommand::stmt()
{
    return m_stmt;
}

DbConnection & SqlCommand::dbConnection()
{
    return m_dbConnection;
}

bool SqlCommand::executeQuery( DbString & query )
{
    if (! m_isInitialized)
        return false;

    if (query.hasError())
    {
        m_errorStr = query.errorString();
        errorBox( m_errorStr );
        return false;
    }

    if (sqlite3_step( m_stmt ) != SQLITE_DONE)
    {
        m_errorStr = sqlite3_errmsg( m_dbConnection.connectionPtr() );
        errorBox( m_errorStr );
        _exec ( "ROLLBACK", m_errorStr );
        return false;
    }

    return true;
}

bool SqlCommand::selectNewId( const QString & tableName, qint64 & newId )
{
    QString sqlStr = QString( "SELECT value FROM ids WHERE tableName = '%1'" ).arg( tableName );
    if (sqlite3_prepare( m_dbConnection.connectionPtr(), sqlStr.toUtf8().data(), -1, & m_stmt, NULL) != SQLITE_OK)
    {
        errorBox( sqlite3_errmsg( m_dbConnection.connectionPtr() ) );
        return false;
    }

    newId = 1;
    switch (sqlite3_step( m_stmt ))
    {
    case SQLITE_DONE:
        sqlStr = QString( "INSERT INTO ids ( tablename, value ) VALUES ( '%1', %2 )" ).arg( tableName ).arg( newId );
        break;

    case SQLITE_ROW:
        newId = sqlite3_column_int64( m_stmt, 0 ) + 1;
        sqlStr = QString( "UPDATE ids SET value = %1 WHERE tableName = '%2'" ).arg( newId ).arg( tableName );
        break;

    default:
        errorBox( sqlite3_errmsg( m_dbConnection.connectionPtr() ) );
        return false;
    }

    if (sqlite3_reset( m_stmt ) != SQLITE_OK)
    {
        errorBox( sqlite3_errmsg( m_dbConnection.connectionPtr() ) );
        return false;
    }

    QString errorStr;
    if (! _exec( sqlStr, errorStr ))
    {
        errorBox( errorStr );
        return false;
    }

    return true;
}

bool SqlCommand::commit()
{
    if (! m_errorStr.isEmpty())
    {
        errorBox( m_errorStr );
        return false;
    }

    QString errorStr;
    if (! _exec( "COMMIT", errorStr ))
    {
        m_errorStr = QString( "Db commit failed: %1" ).arg( errorStr );
        errorBox( m_errorStr );
        return false;
    }

    m_commited = true;
    return true;
}

bool SqlCommand::init()
{
    // open transaction
    QString errorStr;
    if (! _exec( "BEGIN", errorStr ))
    {
        errorBox( QString( "Transaction BEGIN command failed: %1" ).arg( errorStr ) );
        return false;
    }

    return true;
}

bool SqlCommand::_exec( const QString & sqlStr, QString & errorStr )
{
    char * errorMessage = NULL;
    if (sqlite3_prepare( m_dbConnection.connectionPtr(), sqlStr.toUtf8().data(), -1, & m_stmt, NULL) != SQLITE_OK)
    {
        errorStr = sqlite3_errmsg( m_dbConnection.connectionPtr() );
        return false;
    }

    if (sqlite3_step( m_stmt ) != SQLITE_DONE)
    {
        errorStr = sqlite3_errmsg( m_dbConnection.connectionPtr() );
        return false;
    }

    if (sqlite3_reset( m_stmt ) != SQLITE_OK)
    {
        errorStr = sqlite3_errmsg( m_dbConnection.connectionPtr() );
        return false;
    }

    return true;
}

//////////////////////////////////////// Selectors /////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////

Selector::Selector()
:   m_dbConnection( DbConnection::dbConnection() ),
    m_stmt( NULL ),
    m_actualRow( 0 ),
    m_isEmpty( false )
{
}

Selector::Selector( DbConnection & dbConnection )
:   m_dbConnection( dbConnection ),
    m_stmt( NULL ),
    m_actualRow( 0 ),
    m_isEmpty( false )
{
}

Selector::~Selector()
{
}

sqlite3_stmt *& Selector::stmt()
{
    return m_stmt;
}

DbConnection & Selector::dbConnection()
{
    return m_dbConnection;
}

bool Selector::select( DbString & commandStr, bool bShowError )
{
    if (commandStr.hasError())
    {
        if (bShowError)
            errorBox( commandStr.errorString() );

        return false;
    }

    int result = sqlite3_step( m_stmt );
    switch (result)
    {
    case SQLITE_ROW:
        m_isEmpty = false;
        break;

    case SQLITE_DONE:
        m_isEmpty = true;
        break;

    default:
        if (bShowError)
            errorBox( sqlite3_errmsg( m_dbConnection.connectionPtr() ) );

        return false;
    }

    m_actualRow = -1;
    return true;
}

bool Selector::isEmptySelect()
{
    return m_isEmpty;
}

bool Selector::_isNull( int column )
{
    return ( sqlite3_column_type( m_stmt, column ) == SQLITE_NULL );
}

qint64 Selector::_getInt64( int column )
{
    return sqlite3_column_int64( m_stmt, column );
}

qint32 Selector::_getInt32( int column )
{
    return sqlite3_column_int( m_stmt, column );
}

double Selector::_getDouble( int column )
{
    return sqlite3_column_double( m_stmt, column );
}

QString Selector::_getString( int column )
{
    const unsigned char * pValue = sqlite3_column_text( m_stmt, column );
    return QString::fromUtf8( (const char*)pValue );
}

QDate Selector::_getDate( int column )
{
    int value = sqlite3_column_int( m_stmt, column );
    return QDate::fromJulianDay( value );
}

QDateTime Selector::_getDateTime( int column )
{
    qint64 value = sqlite3_column_int64( m_stmt, column );
    return QDateTime::fromMSecsSinceEpoch( value );
}

QByteArray Selector::_getBytea( int column )
{
    const void * pValue = sqlite3_column_blob( m_stmt, column );
    int bytesCount = sqlite3_column_bytes( m_stmt, column );
    return QByteArray( (const char*)pValue, bytesCount );
}

bool Selector::isNull( int column )
{
    return _isNull( column );
}

qint64 Selector::getInt64( int column )
{
    return _getInt64( column );
}

qint32 Selector::getInt32( int column )
{
    return _getInt32( column );
}

double Selector::getDouble( int column )
{
    return _getDouble( column );
}

QString Selector::getString( int column )
{
    return _getString( column );
}

QDate Selector::getDate( int column )
{
    return _getDate( column );
}

QDateTime Selector::getDateTime( int column )
{
    return _getDateTime( column );
}

QByteArray Selector::getBytea( int column )
{
    return _getBytea( column );
}

qint64 Selector::getNvlInt64( int column, qint64 defaultValue )
{
    return _isNull( column ) ? defaultValue : _getInt64( column );
}

qint32 Selector::getNvlInt32( int column, qint32 defaultValue )
{
    return _isNull( column ) ? defaultValue : _getInt32( column );
}

double Selector::getNvlDouble( int column, double defaultValue )
{
    return _isNull( column ) ? defaultValue : _getDouble( column );
}

QString Selector::getNvlString( int column, const QString & defaultValue )
{
    return _isNull( column ) ? defaultValue : _getString( column );
}

QDate Selector::getNvlDate( int column, const QDate & defaultValue )
{
    return _isNull( column ) ? defaultValue : _getDate( column );
}

QDateTime Selector::getNvlDateTime( int column, const QDateTime & defaultValue )
{
    return _isNull( column ) ? defaultValue : _getDateTime( column );
}

QByteArray Selector::getNvlBytea( int column, const QByteArray & defaultValue )
{
    return _isNull( column ) ? defaultValue : _getBytea( column );
}

bool Selector::read()
{
    m_actualRow ++;
    if (! m_isEmpty && m_actualRow == 0)
        return true;

    return ( sqlite3_step( m_stmt ) == SQLITE_ROW );
}
